/*******************************************************************************
 * Copyright 2011-2012 Dik Grapendaal
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package sh.grapendaal.tsuushin.socket;

import java.util.ArrayList;
import java.util.List;

import org.jboss.netty.channel.ChannelEvent;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
import org.jboss.netty.channel.WriteCompletionEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import sh.grapendaal.tsuushin.socket.IrcSocket.DisconnectReason;
import sh.grapendaal.tsuushin.socket.IrcSocket.Listener;

public class IrcClientHandler extends SimpleChannelHandler {
	private static final Logger logger = LoggerFactory.getLogger(IrcClientHandler.class);

	private List<Listener> listeners = new ArrayList<Listener>();

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void handleUpstream(ChannelHandlerContext context, ChannelEvent event) throws Exception {
		// Here for now, to see what's going on. Will be removed.
		logger.info(event.toString());
		super.handleUpstream(context, event);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void messageReceived(ChannelHandlerContext context, MessageEvent event) {
		dispatchReceive((String) event.getMessage());
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void exceptionCaught(ChannelHandlerContext context, ExceptionEvent event) {
		logger.error("Exception caught in IrcClientHandler", event.getCause());
		event.getChannel().close();

		dispatchDisconnect(DisconnectReason.CONNECTION_RESET);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void channelConnected(ChannelHandlerContext context, ChannelStateEvent event) throws Exception {
		dispatchConnect();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void channelDisconnected(ChannelHandlerContext context, ChannelStateEvent event) throws Exception {
		dispatchDisconnect(DisconnectReason.CONNECTION_CLOSED);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void disconnectRequested(ChannelHandlerContext context, ChannelStateEvent event) throws Exception {
		if (context.getChannel().isConnected()) {
			// TODO Add configured disconnect reason.
			event.getChannel().write("QUIT\r\n");
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void writeRequested(ChannelHandlerContext context, MessageEvent event) throws Exception {
		logger.info(event.toString());

		// TODO I actually want this to happen in writeComplete(), but the sent
		// message is not available in there. It's gonna require some coding to
		// get round that.. neatly.
		dispatchSend(event.toString());
		
		super.writeRequested(context, event);
	}

	@Override
	public void writeComplete(ChannelHandlerContext context, WriteCompletionEvent event) throws Exception {
		super.writeComplete(context, event);
	}

	/**
	 * Adds an object that wants to receive events from the Socket class.
	 * 
	 * @param listener
	 *            The listener to add.
	 */
	public void addListener(Listener listener) {
		listeners.add(listener);
	}

	/**
	 * Removes the listener object that was added.
	 * 
	 * @param listener
	 *            The listener to remove.
	 */
	public void removeListener(Listener listener) {
		listeners.remove(listener);
	}

	/**
	 * Dispatches the connect event to all registered listeners.
	 */
	private void dispatchConnect() {
		for (Listener listener : listeners) {
			listener.onConnect();
		}
	}

	/**
	 * Dispatches the receive event to all registered listeners.
	 * 
	 * @param line
	 *            The line that was received.
	 */
	private void dispatchReceive(String line) {
		for (Listener listener : listeners) {
			listener.onReceive(line);
		}
	}

	/**
	 * Dispatches the send event to all registered listeners.
	 * 
	 * @param line
	 *            The line that was sent.
	 */
	private void dispatchSend(String line) {
		for (Listener listener : listeners) {
			listener.onSend(line);
		}
	}

	/**
	 * Dispatches the disconnect event to all registered listeners.
	 * 
	 * @param reason
	 *            The reason as to why we're disconnected.
	 */
	private void dispatchDisconnect(DisconnectReason reason) {
		for (Listener listener : listeners) {
			listener.onDisconnect(reason);
		}
	}
}
